package cn.tedu.v2_1;

import java.io.*;
import java.net.BindException;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * Version 2.1 多人聊天室
 * 知识点: 多线程并发问题
 * 实现步骤: 1.Server类--创建多线程CS: 目的:处理多个客户端发来的链接
 *         1.1 找到Server类，在其主方法下: 加入内部类多线程CS，
 *             目的: 供本类任意地方使用---多线程CS
 *             结构: private class CS implements Runnable{}
 *         1.2 在内部类多线程CS---私有化属性: Socket---表示线程中socket对象
 *             在CS类多线程类: private Socket socket;
 *         1.3 创建构造器，目的: 通过线程创建---传入一个通过IP+端口号链接成功客户端socket
 *             public CS(Socket s){this.socket=s;}
 *             参数: Socket s---start启动方法中: 获取通过IP+端口
 *             返回正确客户端 socket
 *             理解: 已经拨通服务器端的客户端
 *             语法块: this.socket=s;
 *             理解: 当前线程中socket=获取通过IP+端口，返回正确客户端
 *         1.4 如果已经通过IP+端口得到socket当中正确进行读入
 *             理解: 来一个客户端---服务器端分配: 专门socket，进行读入操作
 *             对应: CS线程中 run()  实现读入功能
 *
 *          2.Server类--读入操作, 存入线程中
 *                   start方法: 第一件事情: 无限次接收客户端
 *                             第二件事情: 利用线程---客户端分配正确链接
 *                                        进行操作
 */
public class Server {
    private ServerSocket serverSocket;
    public Server(){
        try {
            serverSocket=new ServerSocket(6666);
        } catch (BindException e){
            System.out.println("端口号重复申请，建议换个端口号");
        } catch (IOException e) {
            System.out.println("申请端口号失败...");
        }
    }
    //二、服务器端启动: 第一件事情: 接收服务器链接
    //               第二件事情: CS线程类: 处理所有链接服务器端客户端
    //                         必须处理各个客户端的写出功能--【读取】
    public void start()  {
        try {
            while (true){
            // 第一件事情: 无限次接收客户端链接，返回客户端socket
            System.out.println("-------等待客户端链接------");
            Socket socket = serverSocket.accept();
            String ip=socket.getInetAddress().getHostAddress();
            System.out.println("一个客户端链接成功了~~~");
            //第二件事情: 接收一个客户端链接---交给线程处理---读入功能
            //效果: 链接上一个客户端---有一个服务器端，正确读入操作
            //多线程并发解决方案: 来客户端---服务器使用线程---创建客户端链接
            CS cs=new CS(socket);      //1.通过链接
            Thread t1=new Thread(cs);  //2.交给线程---读入功能
            t1.start();                //3.启动线程
            }
        } catch (IOException e) {
            System.out.println("服务器端通讯断开");
        } finally {
        }


    }
    public static void main(String[] args)  {
        Server server=new Server();
        server.start();
    }
    //一、创建多线程CS
    //1.多线程CS类: 处理多个客户端连接的线程类---读入存入线程中
    //使用内部类的原因: 多线程在Server中可以随时使用---读入方法
    //问题: 一个客户端链接服务器，需要流操作: 一个客户端与一个服务器端说话【卡住】
    //把服务器端写---放入线程中---目的: 来一个客户端---配上服务器写
    private class CS implements Runnable{
        //3.定义一个私有属性: 给当前多线程类使用
        private Socket socket;  //返回一个客户端通讯: socket

        //4.多个客户端---交给CS构造器确定---那个客户端---进行读入操作
        public CS(Socket s){
            //5.通过线程创建---传入socket---确定就是当前线程socket
            this.socket=s;
        }

        //2.服务器端功能: 读入---加入线程中
        public void run() {
            BufferedReader br=null;
            try {
            InputStream is = socket.getInputStream();
            InputStreamReader isr =
                    new InputStreamReader(is,"UTF8");
            br=new BufferedReader(isr);
            String str=null;
            while (true){
                str=br.readLine();
                System.out.println("客户端说的话: "+str);
            }
            } catch (NullPointerException e) {
                System.out.println("IO流传输对象，有对象为null,请立马核对。"
                        +"建议: 查看try块中对象可能赋值为null");
            } catch (UnsupportedEncodingException e) {
                System.out.println("客户端或者服务器端: 字符集不一致");
            } catch (IOException e) {
                System.out.println("通讯过程中，传输出现错误!");
            } finally {  //一定执行代码块: 关闭流、关闭通讯
                if (br!=null)
                    try {
                        br.close();
                    } catch (IOException e) {
                        System.out.println("结束通讯: 关闭失败!");
                    }
                if (socket!=null)
                    try {
                        socket.close();
                    } catch (IOException e) {
                        System.out.println("通讯关闭失败~");
                    }
            }
        }
    }
}
